package com.example.demo.aspectj;

import com.alibaba.fastjson.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
@Order(111115)
public class AccessLoggerAspectJ {
    /**
     * 通过自动装配 拿到request及response
     * 切点定义的不同，可能导致自动装配失败，这里定义required=false，忽略无法装配的情况
     */
    @Autowired(required=false)
    HttpServletRequest request;
    @Autowired(required=false)
    HttpServletResponse response;
    /** 切点 */
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) " +
            "|| @within(org.springframework.stereotype.Controller)")
    public void cbpcAccessLoggerAspectJ(){}

    /**
     * 环绕通知 返回通知
     * @param res 响应内容
     */
    @Around(value = "cbpcAccessLoggerAspectJ()")
    public Object  doAfterReturning(ProceedingJoinPoint pjd) throws Throwable {
        Object result = null;
        boolean errflag = false;
        try {
            result = pjd.proceed(pjd.getArgs());
        }catch (Throwable e) {
            errflag = true;
            throw e;
        }finally {
            sendLog(pjd.getSignature().toString(),pjd.getArgs(),result,errflag);
        }
        return result;
    }

    private void sendLog(String signature,Object[] args, Object returnVal, boolean errorflag) {
        String traceId = MDC.get(LoggerConstant.TRACE_ID);
        LoggerDTO dto  = new LoggerDTO();
        dto.setAccResUri(request.getRequestURL().toString());
        dto.setAccResReqParam(getParams(args));
        dto.setAccResType(limitLength(signature,500));
        dto.setAccRemoteIp(request.getRemoteAddr());
        dto.setAccRemoteAgent(limitLength(request.getHeader("User-Agent"),500));
        dto.setTraceId(traceId);
        dto.setAccResRespType(limitLength(returnVal!=null ? returnVal.getClass().getName():Void.class.getName(),500));
        dto.setAccResRespContext(getParams(returnVal));
        dto.setAccResResStatus(errorflag?"ERROR":"SUCCESS");
        dto.setAccUserName("");
        dto.setAccUserId("");
        dto.setAccOpTime(new Date());
        dto.setAccType(request.getMethod());
        dto.setRemark("");
        log.info("upload:{}",dto);
    }
    private String getParams(Object... args) {
        if (args != null && args.length > 0) {
            String res = "";
            try {
                List<Object> argsList = Arrays.asList(args);
                // 将 HttpServletResponse 和 HttpServletRequest 参数移除，不然会报异常
                List<Object> collect = argsList.stream().filter(o ->
                        !(o instanceof ServletResponse
                                || o instanceof ServletRequest
                                || o instanceof HandlerMethod
                                || o instanceof InputStreamResource
                                || o instanceof File
                                || o instanceof Throwable
                                || o instanceof HttpSession)).collect(Collectors.toList());
                res = JSONArray.toJSONString(collect);
            }catch (Exception e) {
                if(log.isDebugEnabled()) {
                    log.debug("日志记录异常，参数处理失败", e);
                }
                for (Object arg : args) {
                    res += (arg.getClass().getName() + "|");
                }
            }
            return limitLength(res, 2048);
        }
        return null;
    }
    private String limitLength(String res,int length){
        return res != null && res.length() > length?res.substring(0,length):res;
    }
}
